package service

import (
	"bufio"
	"context"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"runtime"
	"strings"

	"github.com/ncruces/zenity"
	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

var uploadflag = []byte{'z', 'z', 'z', '\r'}

func startSsh(serverInfo ServerInfo) error {
	// SSH配置
	config := &ssh.ClientConfig{
		User:            serverInfo.User,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	if serverInfo.Password != "" {
		config.Auth = []ssh.AuthMethod{ssh.Password(serverInfo.Password)}
	} else {
		var signer ssh.Signer

		if serverInfo.PrivateKey == "" {
			homePath, err := os.UserHomeDir()
			if err != nil {
				return err
			}
			key, err := os.ReadFile(homePath + "/.ssh/id_rsa")
			if err != nil {
				return err
			}
			signer, err = ssh.ParsePrivateKey(key)
			if err != nil {
				return err
			}
		} else {
			var err error
			signer, err = ssh.ParsePrivateKey([]byte(serverInfo.PrivateKey))
			if err != nil {
				return err
			}
		}

		config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
	}

	// 建立SSH链接
	host := serverInfo.Host
	if !strings.ContainsRune(host, ':') {
		host = host + ":22"
	}
	client, err := ssh.Dial("tcp", host, config)
	if err != nil {
		return err
	}
	defer client.Close()

	// 打开一个新会话
	session, err := client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()

	termType := os.Getenv("TERM")
	if termType == "" {
		termType = "xterm-256color"
	}

	fd := int(os.Stdin.Fd())
	if !terminal.IsTerminal(fd) {
		osName := runtime.GOOS
		return fmt.Errorf("%s fd %d is not a terminal,can't create pty of ssh", osName, fd)
	}
	state, err := terminal.MakeRaw(fd)
	if err != nil {
		return err
	}
	defer terminal.Restore(fd, state)

	termWidth, termHeight, err := terminal.GetSize(fd)
	if err != nil {
		return err
	}

	err = session.RequestPty(termType, termHeight, termWidth, ssh.TerminalModes{})
	if err != nil {
		return err
	}

	// 设置输入和输出
	session.Stdout = os.Stdout
	session.Stderr = os.Stderr
	// session.Stdin = os.Stdin

	// 对输入进行定制
	write, err := session.StdinPipe()
	if err != nil {
		return err
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func(ctx context.Context) {
		var err error
		var b byte
		inputReader := bufio.NewReader(os.Stdin)
		flag := make([]byte, len(uploadflag), len(uploadflag)+1)
		for {

			b, err = inputReader.ReadByte()
			if err != nil {
				if errors.Is(err, io.EOF) {
					break
				}
				panic(err)
			}
			// 这边还是要考虑下咋写
			// 上面的代码会block住导致函数退出不及时
			select {
			case <-ctx.Done():
				return
			default:
				flag = append(flag, b)
				flag = flag[1:]
				if reflect.DeepEqual(uploadflag, flag) {

					var filepath string
					filepath, err = zenity.SelectFile()
					if err != nil {
						continue
					}

					// upload file
					var remoteTmpPath string
					remoteTmpPath, err = uploadFileTmp(client, filepath)
					if err != nil {
						panic(err)
					}

					move := "\rmv " + remoteTmpPath + " ./"
					_, err = write.Write([]byte(move))
					if err != nil {
						panic(err)
					}

				}
				_, err = write.Write([]byte{b})
				if err != nil {
					panic(err)
				}
			}
		}

	}(ctx)

	// 启动shell
	err = session.Shell()
	if err != nil {
		return err
	}

	// 等待会话结束
	err = session.Wait()
	if err != nil {
		return err
	}

	return nil
}

func uploadFileTmp(sshClient *ssh.Client, filepath string) (string, error) {
	sftpClient, err := sftp.NewClient(sshClient, sftp.UseConcurrentWrites(true), sftp.UseConcurrentReads(true))
	if err != nil {
		return "", err
	}
	defer sftpClient.Close()

	// 读取本地文件内容
	localFile, err := os.Open(filepath)
	if err != nil {
		return "", err
	}
	defer localFile.Close()
	localBytes, err := ioutil.ReadAll(localFile)
	if err != nil {
		return "", err
	}

	filename := filepath[strings.LastIndex(filepath, string([]rune{os.PathSeparator}))+1:]
	filename = "/tmp/" + filename

	remoteFile, err := sftpClient.Create(filename)
	if err != nil {
		return "", err
	}
	defer remoteFile.Close()
	_, err = remoteFile.Write(localBytes)
	if err != nil {
		return "", err
	}
	return filename, nil
}
